
/****************************************************************************/
/*Copyright (c) 2011, Florent DEVILLE.                                      */
/*All rights reserved.                                                      */
/*                                                                          */
/*Redistribution and use in source and binary forms, with or without        */
/*modification, are permitted provided that the following conditions        */
/*are met:                                                                  */
/*                                                                          */
/* - Redistributions of source code must retain the above copyright         */
/*notice, this list of conditions and the following disclaimer.             */
/* - Redistributions in binary form must reproduce the above                */
/*copyright notice, this list of conditions and the following               */
/*disclaimer in the documentation and/or other materials provided           */
/*with the distribution.                                                    */
/* - The names of its contributors cannot be used to endorse or promote     */
/*products derived from this software without specific prior written        */
/*permission.                                                               */
/* - The source code cannot be used for commercial purposes without         */ 
/*its contributors' permission.                                             */
/*                                                                          */
/*THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS       */
/*"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT         */
/*LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS         */
/*FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE            */
/*COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,       */
/*INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,      */
/*BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;          */
/*LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER          */
/*CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT        */
/*LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN         */
/*ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE           */
/*POSSIBILITY OF SUCH DAMAGE.                                               */
/****************************************************************************/

#include "RTPrimCone.h"


#include <math.h>

namespace RT
{
	/*Constructor*/
	RTPrimCone::RTPrimCone(F32 radius, F32 height):RTIPrimitive(),
		m_radius(radius), m_height(height)
	{
		m_angle = atan(m_radius / m_height);
		m_tanAngle = m_radius / m_height;
		m_tanAnglePowerTwo = m_tanAngle * m_tanAngle;
	}

	/*Destructor*/
	RTPrimCone::~RTPrimCone(){}

	/*Check intersection between a ray and the cone*/
	bool RTPrimCone::intersect(const RTRay& ray, F32& dist, bool useGlobalCoordinates)
	{
		//change the coordinate system
		RTRay modRay;
		if (useGlobalCoordinates)
			globalToLocal(ray, modRay);
		else
			convertRayToLocalCoordinates(ray, modRay);

		//calulate A B and C in At^2 + Bt + C = 0
		//it comes from the cone formula : x^2 + z^2 = y^2 * tan(a)^2

		F32 A = modRay.getDirection().x * modRay.getDirection().x + modRay.getDirection().z * modRay.getDirection().z -
			modRay.getDirection().y * modRay.getDirection().y * m_tanAnglePowerTwo;

		F32 B = 2 * (modRay.getDirection().x * modRay.getOrigin().x + modRay.getDirection().z * modRay.getOrigin().z - 
			modRay.getDirection().y * modRay.getOrigin().y * m_tanAnglePowerTwo);

		F32 C = modRay.getOrigin().x * modRay.getOrigin().x + modRay.getOrigin().z * modRay.getOrigin().z - 
			modRay.getOrigin().y * modRay.getOrigin().y * m_tanAnglePowerTwo;

		//calculate the solution ot the equation
		F32 delta = B * B - 4 * A * C;

		//for the degenerate poI32
		if(delta > -0.001f && delta < 0.001f)
			delta = 0;

		if(delta > 0)
		{
			F32 sqrtDelta = sqrt(delta);

			F32 t1 = (-B + sqrtDelta) / (2 * A);
			F32 t2 = (-B - sqrtDelta) / (2 * A);

			F32 H1 = modRay.getOrigin().y + modRay.getDirection().y * t1;
			F32 H2 = modRay.getOrigin().y + modRay.getDirection().y * t2;

			if(!isValideHeight(H1) && !isValideHeight(H2))
				return false;

			if(isValideHeight(H1) && isValideHeight(H2))
			{
				if(t1 <= 0 && t2 <= 0)
					return false;
				else if(t1 <= 0)
					dist = t2;
				else if(t2 <= 0)
					dist = t1;
				else
					dist = fmin(t1, t2);
				return true;
			}
			else if(isValideHeight(H1))
				dist = t1;
			else 
				dist = t2;

			//check for the top end
			F32 tEnd = calculateTTopEnd(modRay);
			if(tEnd < dist && tEnd > 0)
				dist = tEnd;

			if(dist < 0)
				return false;

			return true;

		}
		else if (delta == 0)
		{
			//top end
			F32 tEnd = calculateTTopEnd(modRay);

			//tangent to the cone
			dist = -B / (2 * A);
		
			if(tEnd < dist && tEnd > 0)
				dist = tEnd;

			F32 H = modRay.getOrigin().y + modRay.getDirection().y * dist;
			if(!isValideHeight(H))
				return false;

			return true;
		}
		return false;
	}

	/*Calculate the normal of the cone for an intersecting ray*/
	void RTPrimCone::computeNormal(const RTRay& ray, F32 dist, RTVector3f& normal)const
	{
		//change the coordinate system
		RTRay R;
		convertRayToLocalCoordinates(ray, R);

		//calculate intersection poI32
		RTVector3f intersection = R.getOrigin() + R.getDirection() * dist;

		//if close from 0
		if(intersection.y < 0.001f && intersection.y > -0.001f)
			intersection.y = 0;

		//if valid height
		if(intersection.y + 0.001 < m_height && intersection.y != 0)
		{
			normal = intersection;
			normal.y = 0;
			normal.normalize();

			//rotation axis
			RTVector3f R;

			if(normal.x != 0)
				if(normal.z < 0 && normal.x < 0) //both negative
					R = RTVector3f(normal.z/normal.x, 0, -1);
				else if(normal.z < 0) // z negative and x positive
					R = RTVector3f(-normal.z/normal.x, 0, 1);
				else if(normal.x < 0) //z positif and x negative
					R = RTVector3f(normal.z/normal.x, 0, -1);
				else //both positive
					R = RTVector3f(-normal.z/normal.x, 0, 1);
			else
				if(normal.z < 0)
					R = RTVector3f(1, 0, 0);
				else
					R = RTVector3f(-1, 0, 0);

			R.normalize();

			//create a matrix rotation
			RTMatrix44 rotationMatrix;
			rotationMatrix.createRotation(R, m_angle/2);

			normal = normal * rotationMatrix;
		}
		else if (intersection.y == 0)
			normal = RTVector3f(0, -1, 0);
		else
			normal = RTVector3f(0, 1, 0);

		//if ray from the inside
		if(R.getDirection().dot(normal) > 0)
			normal = normal * -1;

		//reset to the global coordinate systeme
		convertNormalToGlobalCoordinates(normal, normal);


	}

	/*Check if the point is inside the cone*/
	bool RTPrimCone::isInside(const RTVector3f& p)const
	{
		//convert p to local coordinates
		RTVector3f localP;
		convertPointToLocalCoordinates(p, localP);

		//check if localP is high enough
		if(!isValideHeight(localP.y))
			return false;

		//get the radius at the specific height
		F64 radius = m_tanAnglePowerTwo * localP.y * localP.y;

		//check if inside the circle
		if(localP.x * localP.x + localP.z * localP.z <= radius)
			return true;

		return false;

	}

	/*Check if the height is valid : inside the height of the cone.*/
	bool RTPrimCone::isValideHeight(F32 h)const
	{
		if(h >= 0 && h <= m_height)
			return true;

		return false;
	}

	/*Return the minimum between f1 and f2.*/
	F32 RTPrimCone::fmin(F32 f1, F32 f2)const
	{
		if(f1 < f2)
			return f1;

		return f2;
	}

	/*Calculate intersection betwwen the ray and the cone cap.*/
	F32 RTPrimCone::calculateTTopEnd(const RTRay& R)const
	{
		return (m_height - R.getOrigin().y)/R.getDirection().y;
	}

}